#include "StdAfx.h"
#include "Monitor.h"

#include <process.h>

CMonitor::CMonitor(void) :
	m_nID(0),
	m_IoPort(NULL),
	m_hTarget(NULL),
	m_hThread(NULL),
	m_pstMonitorInfo(NULL),
	m_bIsStarted(FALSE)
{
	ZeroMemory(m_szDirName, sizeof(TCHAR) * _countof(m_szDirName));
}

CMonitor::~CMonitor(void)
{
	Stop();
}

int CMonitor::Start(TCHAR *pszDir)
{
	int nRet = MONITOR_ERR_SUCCESS;

	if(m_bIsStarted)
	{
		nRet = MONITOR_WARN_ALREADY_STARTED;
		goto _EXIT;
		
	}

	_tcsncpy_s(m_szDirName, MAX_PATH, pszDir, _tcslen(pszDir));

	// Get a directory handle to be monitored
	m_hTarget = CreateFile(m_szDirName,
						   FILE_LIST_DIRECTORY,
						   FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE,
						   NULL,
						   OPEN_EXISTING,
						   FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
						   NULL);
	if(m_hTarget == INVALID_HANDLE_VALUE)
	{
		nRet = MONITOR_ERR_FAIL_TO_OPEN_DIR;
		goto _EXIT;
	}

	// Allocate memory for an information of monitor
	m_pstMonitorInfo = new MONITOR_INFO;
	ASSERT(m_pstMonitorInfo != NULL);
	if(m_pstMonitorInfo == NULL)
	{
		nRet = MONITOR_ERR_OUT_OF_MEMORY;
		goto _EXIT;
	}

	m_IoPort = CreateIoCompletionPort(m_hTarget, m_IoPort, (DWORD)m_pstMonitorInfo, 0);
	if(m_IoPort == INVALID_HANDLE_VALUE)
	{
		TRACE(_T("Failed to create IoCompletionPort.\n"));
		nRet = MONITOR_ERR_FAIL_TO_CREATE_IOPORT;
		goto _EXIT;
	}
	
	if(_TryMonitoring() == FALSE)
	{
		nRet = MONITOR_ERR_FAIL_TO_MONITOR;
		goto _EXIT;
	}

	m_hThread = (HANDLE)_beginthreadex(NULL, 0, MonitorThread, this, 0, NULL);
	if(m_hThread == NULL)
	{
		TRACE(_T("Failed to create Monitor thread.\n"));
		nRet = MONITOR_ERR_FAIL_TO_CREATE_THREAD;
		goto _EXIT;
	}

	m_bIsStarted = TRUE;

_EXIT:
	if(nRet != MONITOR_ERR_SUCCESS)
	{
		Stop();
	}

	return nRet;
}


int CMonitor::Stop()
{
	if(m_IoPort != NULL)
	{
		PostQueuedCompletionStatus(m_IoPort, 0, 0, NULL);
		m_IoPort = NULL;
		::Sleep(100);
	}

	if(m_pstMonitorInfo != NULL)
	{
		delete m_pstMonitorInfo;
		m_pstMonitorInfo = NULL;
	}

	if(m_hTarget != NULL)
	{
		CloseHandle(m_hTarget);
		m_hTarget = NULL;
	}

	if(m_hThread != NULL)
	{
		TerminateThread(m_hThread, 0);
		CloseHandle(m_hThread);
		m_hThread = NULL;
	}

	m_bIsStarted = FALSE;

	return MONITOR_ERR_SUCCESS;
}

BOOL CMonitor::_TryMonitoring()
{
	DWORD	dwReadLen = 0;
	DWORD	dwBufSize = MONITOR_BUF_SIZE;
	DWORD	dwFilter = FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_FILE_NAME |
						FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_ATTRIBUTES;

	ZeroMemory(m_pstMonitorInfo, sizeof(MONITOR_INFO));

	m_pstMonitorInfo->lpExtra = (LPVOID)this;
	if(ReadDirectoryChangesW(m_hTarget, 
							 m_pstMonitorInfo->aBuf, 
							 dwBufSize, 
							 TRUE, 
							 dwFilter,
							 &dwReadLen,
							 &m_pstMonitorInfo->stOverlapped,
							 NULL))
	{
		return TRUE;
	}
	else
	{
		return FALSE;
	}
}


unsigned CMonitor::MonitorThread(LPVOID arg)
{
	CMonitor *pMonitor = NULL;
	DWORD numBytes;
	DWORD cbOffset;
	PMONITOR_INFO pstMonInfo;
	LPOVERLAPPED lpOverlapped;
	PFILE_NOTIFY_INFORMATION pFNI;

	pMonitor = reinterpret_cast<CMonitor *>(arg);

	do
	{
		BOOL bComp = GetQueuedCompletionStatus(pMonitor->m_IoPort, &numBytes, (LPDWORD)&pstMonInfo, &lpOverlapped, INFINITE);
		if((pstMonInfo == NULL) && (lpOverlapped == NULL))
		{
			_endthreadex(0);
			return 0;
		}

		cbOffset = 0;
		do
		{
			pFNI = reinterpret_cast<PFILE_NOTIFY_INFORMATION>((pstMonInfo->aBuf) + cbOffset);
			cbOffset += pFNI->NextEntryOffset;
			/*
			switch(pstNoti->Action)
			{
				case FILE_ACTION_ADDED:
					break;
				case FILE_ACTION_REMOVED:
					break;
				case FILE_ACTION_MODIFIED:
					break;
				case FILE_ACTION_RENAMED_OLD_NAME:
					break;
				case FILE_ACTION_RENAMED_NEW_NAME:
					break;
			}
			*/
			PMONITOR_RESULT mr = new MONITOR_RESULT;
			ASSERT(mr != NULL);
			if(mr == NULL)
			{
				TRACE(_T("Out of memory: memory resource is exhausted!\n"));
				break;
			}

			ZeroMemory(mr, sizeof(MONITOR_RESULT));

			mr->nMonitorID = pMonitor->GetID();
			mr->enChange = static_cast<CHANGEMODE>(pFNI->Action);
			StringCchPrintf(mr->szTarget, MAX_PATH, _T("%s\\%s\0"), pMonitor->m_szDirName, pFNI->FileName);
			::PostMessage(g_ParentWnd->m_hWnd, WM_FILECHANGE_NOTIFY, (WPARAM)mr, (LPARAM)NULL);
			
		} while(pFNI->NextEntryOffset != 0);

		if(pMonitor->_TryMonitoring() == FALSE)
		{
			_endthreadex(0);
			return 0;
		}
	}while(1);
}

TCHAR * CMonitor::ConvertChangeModeToString(CHANGEMODE enCMode)
{
	switch(enCMode)
	{
	case CHANGE_ADD:
		return _T("(1)ADD");
	case CHANGE_REMOVE:
		return _T("(2)REMOVE");
	case CHANGE_MODIFY:
		return _T("(3)MODIFY");
	case CHANGE_RENAME_OLD:
		return _T("(4)RENAME OLD");
	case CHANGE_RENAME_NEW:
		return _T("(5)RENAME NEW");
	default:
		return _T("(0)UNKNOWN");
	}
}
